#pragma once

#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

using namespace LogModule;
using namespace LockModule;
using namespace ThreadPoolModule;

using task_t = function<void()>;
using handler_t = function<string(string)>;

#define BACKLOG 8

static const uint16_t gport = 8080;

class TcpServer
{
    struct ThreadData
    {
        int sockfd;
        TcpServer* self;
    };
public:
    TcpServer(handler_t handler, int port = gport)
        :_handler(handler)
        ,_port(port)
        ,_isrunning(false)
    {}

    void InitServer()
    {
        // 1. 创建tcp socket
        _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);  // tcp 的socket需要流式套接字
        if(_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            Die(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket create success, sockfd is : " << _listensockfd;

        // 2. 绑定bind
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;

        int n = ::bind(_listensockfd, CONV(&local), sizeof(local));
        if(n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            Die(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success, sockfd is : " << _listensockfd;

        // 3. tcp 也是CS模式，就要求tcp随时随地等待被连接
        // tcp需要将socket设置成监听状态
        n = ::listen(_listensockfd, BACKLOG);
        if(n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            Die(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success, sockfd is : " << _listensockfd;

        // ::signal(SIGCHLD, SIG_IGN);  // 子进程退出，OS 会自动回收资源，不用再进行wait
    }

    void HandlerRequest(int sockfd)  // tcp 也是全双工的
    {
        LOG(LogLevel::INFO) << "HandlerRequest, sockfd is : " << sockfd;    
        char inbuffer[4096];
        while (true)
        {
            // 因为TCP是流式套接字，关于流的概念在管道、文件等都有提到，所以可以用read、write来对网络进行读写
            // ssize_t n = ::read(sockfd, inbuffer, sizeof(inbuffer)-1);
            ssize_t n = ::recv(sockfd, inbuffer, sizeof(inbuffer)-1, 0);  // 因为TCP的面向字节流，所以recv的结果可能是不完整的
            if(n > 0)
            {
                // 规定用户发过来的是一串完整的命令string
                LOG(LogLevel::INFO) << inbuffer;
                inbuffer[n] = 0;
                // string echo_str = "server echo# ";
                // echo_str += inbuffer;

                // 处理传入的命令
                string cmd_result = _handler(inbuffer);

                // ::write(sockfd, echo_str.c_str(), echo_str.size());
                ::send(sockfd, cmd_result.c_str(), cmd_result.size(), 0);
            }    
            else if(n == 0)
            {
                // 客户端退出
                LOG(LogLevel::INFO) << "client quit: " << sockfd;
                break;
            }
            else 
            {
                // 读取失败
                break;
            }
        }
        ::close(sockfd);  // fd 泄露问题
    }

    // 因为需要将参数设置为void*，所以不能有this指针，必须设置为static的
    // 但是如果设置成了static的话就调用不了类内的成员函数HandlerRequest了
    // 所以我们创建了一个结构体ThreadData，用来保存需要使用的类内成员HandlerRequest和sockfd，传入通过data传入即可
    static void* ThreadEntry(void* args)  
    {
        pthread_detach(pthread_self());
        ThreadData* data = (ThreadData*)args;
        data->self->HandlerRequest(data->sockfd);
        return nullptr;
    }

    void Start()
    {
        _isrunning = true;
        while(_isrunning)
        {
            // tcp 不能直接读取数据，需要先获取新连接
            struct sockaddr_in peer;
            socklen_t peerlen = sizeof(peer);  // 这个地方一定要注意，不然会出现连接失败、缓冲区出错等问题
            LOG(LogLevel::DEBUG) << "accepting...";
            int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);
            if(sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error, " << "errno is: " << strerror(errno);
                continue;  // 获取连接失败了没关系，再继续找下一个，就像拉客失败了，继续拉下一个，所以仅仅是一个warning错误，continue就行
            }
            // 获取连接成功
            LOG(LogLevel::INFO) << "listen success, sockfd is : " << _listensockfd;
            // peer 是一个client，输出型参数，打印client的信息
            InetAddr addr(peer);
            LOG(LogLevel::INFO) << "client info: " << addr.Addr();

            // 处理单client请求 version-1
            // HandlerRequest(sockfd);
            
            // 多client版本，多进程 version-2
            // pid_t id = fork();
            // if(id == 0)
            // {
            //     // child
            //     // 子进程继承父进程的文件描述符表，父子各一张
            //     // 关闭不必要的文件描述符表，防止fd泄露
            //     ::close(_listensockfd);
            //     // 加了这一句：子进程创建新的子进程（孙子进程），然后让子进程(fock的返回值>0)退出，这表明将孙子进程变成了孤儿进程，就会交给系统(1号进程 )领养
            //     // 这样就解决了阻塞等待的问题，这个孙子进程在进入了HandlerRequest之后也会自己退出,交给系统wait
            //     if(fork() > 0) exit(0);
            //     HandlerRequest(sockfd);
            //     exit(0);
            // }
            // // parent
            // // 父进程也要关闭自己不需要的文件描述符
            // ::close(sockfd);
            // // 如果子进程一直不退出不反悔，父进程是否会一直阻塞等待？？ 不会了！
            // pid_t rid = ::waitpid(id, nullptr, 0);
            // if(rid < 0)
            // {
            //     LOG(LogLevel::WARNING) << "waitpid error";
            // }

            //多线程版本：version——3
            // 主线程和新线程是如何看待文件描述符表的？ 所有进程共享一张！
            // pthread_t tid;
            // ThreadData* data = new ThreadData;
            // data->sockfd = sockfd;
            // data->self = this;
            // pthread_create(&tid, nullptr, ThreadEntry, data);

            // 线程池版本：version-4  比较适合处理短任务，或者是任务量较少的情况
            task_t f = bind(&TcpServer::HandlerRequest, this, sockfd);  // 构建任务
            ThreadPool<task_t>::getInstance()->Equeue(f);
        }
    }

    void Stop()
    {
        _isrunning = false;
    }

    ~TcpServer(){}
private:
    int _listensockfd;  // 监听socket
    uint16_t _port;  // 端口号
    bool _isrunning;  // 是否正在运行
    handler_t _handler;  // 处理上层任务的接口
};